#
# This file is part of MiSoC and has been adapted/modified for LiteEth.
#
# Copyright (c) 2018 Sebastien Bourdeauducq <sb@m-labs.hk>
# Copyright (c) 2020 Florent Kermarrec <florent@enjoy-digital.fr>
# SPDX-License-Identifier: BSD-2-Clause

from migen import *
from migen.genlib.resetsync import AsyncResetSynchronizer
from migen.genlib.cdc import PulseSynchronizer

from liteeth.phy.a7_gtp import *
from liteeth.phy.pcs_1000basex import *


class Open(Signal):
    pass


class Gearbox(Module):
    def __init__(self):
        self.tx_data      = Signal(10)
        self.tx_data_half = Signal(20)
        self.rx_data_half = Signal(20)
        self.rx_data      = Signal(10)

        # TX
        buf = Signal(20)
        self.sync.eth_tx += buf.eq(Cat(buf[10:], self.tx_data))
        self.sync.eth_tx_half += self.tx_data_half.eq(buf)

        # RX
        phase_half = Signal()
        phase_half_rereg = Signal()
        self.sync.eth_rx_half += phase_half_rereg.eq(phase_half)
        self.sync.eth_rx += [
            If(phase_half == phase_half_rereg,
                self.rx_data.eq(self.rx_data_half[10:])
            ).Else(
                self.rx_data.eq(self.rx_data_half[:10])
            ),
            phase_half.eq(~phase_half),
        ]


class A7_1000BASEX(Module):
    dw          = 8
    tx_clk_freq = 125e6
    rx_clk_freq = 125e6
    def __init__(self, qpll_channel, data_pads, sys_clk_freq):
        pcs = PCS(lsb_first=True)
        self.submodules += pcs

        self.sink    = pcs.sink
        self.source  = pcs.source
        self.link_up = pcs.link_up

        self.clock_domains.cd_eth_tx      = ClockDomain()
        self.clock_domains.cd_eth_rx      = ClockDomain()
        self.clock_domains.cd_eth_tx_half = ClockDomain(reset_less=True)
        self.clock_domains.cd_eth_rx_half = ClockDomain(reset_less=True)

        # for specifying clock constraints. 62.5MHz clocks.
        self.txoutclk = Signal()
        self.rxoutclk = Signal()

        # # #

        # GTP transceiver
        tx_reset       = Signal()
        tx_mmcm_locked = Signal()
        tx_data        = Signal(20)
        tx_reset_done  = Signal()

        rx_reset          = Signal()
        rx_mmcm_locked    = Signal()
        rx_data           = Signal(20)
        rx_reset_done     = Signal()
        rx_pma_reset_done = Signal()

        drpaddr = Signal(9)
        drpen   = Signal()
        drpdi   = Signal(16)
        drprdy  = Signal()
        drpdo   = Signal(16)
        drpwe   = Signal()


        # Work around Python's 255 argument limitation.
        gtp_params = dict(
            # Simulation-Only Attributes
            p_SIM_RECEIVER_DETECT_PASS   = "TRUE",
            p_SIM_TX_EIDLE_DRIVE_LEVEL   = "X",
            p_SIM_RESET_SPEEDUP          = "FALSE",
            p_SIM_VERSION                = "2.0",

            # RX Byte and Word Alignment Attributes
            p_ALIGN_COMMA_DOUBLE                     = "FALSE",
            p_ALIGN_COMMA_ENABLE                     = 0b1111111111,
            p_ALIGN_COMMA_WORD                       = 1,
            p_ALIGN_MCOMMA_DET                       = "TRUE",
            p_ALIGN_MCOMMA_VALUE                     = 0b1010000011,
            p_ALIGN_PCOMMA_DET                       = "TRUE",
            p_ALIGN_PCOMMA_VALUE                     = 0b0101111100,
            p_SHOW_REALIGN_COMMA                     = "TRUE",
            p_RXSLIDE_AUTO_WAIT                      = 7,
            p_RXSLIDE_MODE                           = "OFF",
            p_RX_SIG_VALID_DLY                       = 10,

            # RX 8B/10B Decoder Attributes
            p_RX_DISPERR_SEQ_MATCH                   = "FALSE",
            p_DEC_MCOMMA_DETECT                      = "FALSE",
            p_DEC_PCOMMA_DETECT                      = "FALSE",
            p_DEC_VALID_COMMA_ONLY                   = "FALSE",

            # RX Clock Correction Attributes
            p_CBCC_DATA_SOURCE_SEL                   = "ENCODED",
            p_CLK_COR_SEQ_2_USE                      = "FALSE",
            p_CLK_COR_KEEP_IDLE                      = "FALSE",
            p_CLK_COR_MAX_LAT                        = 9,
            p_CLK_COR_MIN_LAT                        = 7,
            p_CLK_COR_PRECEDENCE                     = "TRUE",
            p_CLK_COR_REPEAT_WAIT                    = 0,
            p_CLK_COR_SEQ_LEN                        = 1,
            p_CLK_COR_SEQ_1_ENABLE                   = 0b1111,
            p_CLK_COR_SEQ_1_1                        = 0b0100000000,
            p_CLK_COR_SEQ_1_2                        = 0b0000000000,
            p_CLK_COR_SEQ_1_3                        = 0b0000000000,
            p_CLK_COR_SEQ_1_4                        = 0b0000000000,
            p_CLK_CORRECT_USE                        = "FALSE",
            p_CLK_COR_SEQ_2_ENABLE                   = 0b1111,
            p_CLK_COR_SEQ_2_1                        = 0b0100000000,
            p_CLK_COR_SEQ_2_2                        = 0b0000000000,
            p_CLK_COR_SEQ_2_3                        = 0b0000000000,
            p_CLK_COR_SEQ_2_4                        = 0b0000000000,

            # RX Channel Bonding Attributes
            p_CHAN_BOND_KEEP_ALIGN                   = "FALSE",
            p_CHAN_BOND_MAX_SKEW                     = 1,
            p_CHAN_BOND_SEQ_LEN                      = 1,
            p_CHAN_BOND_SEQ_1_1                      = 0b0000000000,
            p_CHAN_BOND_SEQ_1_2                      = 0b0000000000,
            p_CHAN_BOND_SEQ_1_3                      = 0b0000000000,
            p_CHAN_BOND_SEQ_1_4                      = 0b0000000000,
            p_CHAN_BOND_SEQ_1_ENABLE                 = 0b1111,
            p_CHAN_BOND_SEQ_2_1                      = 0b0000000000,
            p_CHAN_BOND_SEQ_2_2                      = 0b0000000000,
            p_CHAN_BOND_SEQ_2_3                      = 0b0000000000,
            p_CHAN_BOND_SEQ_2_4                      = 0b0000000000,
            p_CHAN_BOND_SEQ_2_ENABLE                 = 0b1111,
            p_CHAN_BOND_SEQ_2_USE                    = "FALSE",
            p_FTS_DESKEW_SEQ_ENABLE                  = 0b1111,
            p_FTS_LANE_DESKEW_CFG                    = 0b1111,
            p_FTS_LANE_DESKEW_EN                     = "FALSE",

            # RX Margin Analysis Attributes
            p_ES_CONTROL                             = 0b000000,
            p_ES_ERRDET_EN                           = "FALSE",
            p_ES_EYE_SCAN_EN                         = "FALSE",
            p_ES_HORZ_OFFSET                         = 0x010,
            p_ES_PMA_CFG                             = 0b0000000000,
            p_ES_PRESCALE                            = 0b00000,
            p_ES_QUALIFIER                           = 0x00000000000000000000,
            p_ES_QUAL_MASK                           = 0x00000000000000000000,
            p_ES_SDATA_MASK                          = 0x00000000000000000000,
            p_ES_VERT_OFFSET                         = 0b000000000,

            # FPGA RX Interface Attributes
            p_RX_DATA_WIDTH                          = 20,

            # PMA Attributes
            p_OUTREFCLK_SEL_INV                      = 0b11,
            p_PMA_RSV                                = 0x00000333,
            p_PMA_RSV2                               = 0x00002040,
            p_PMA_RSV3                               = 0b00,
            p_PMA_RSV4                               = 0b0000,
            p_RX_BIAS_CFG                            = 0b0000111100110011,
            p_DMONITOR_CFG                           = 0x000A00,
            p_RX_CM_SEL                              = 0b01,
            p_RX_CM_TRIM                             = 0b0000,
            p_RX_DEBUG_CFG                           = 0b00000000000000,
            p_RX_OS_CFG                              = 0b0000010000000,
            p_TERM_RCAL_CFG                          = 0b100001000010000,
            p_TERM_RCAL_OVRD                         = 0b000,
            p_TST_RSV                                = 0x00000000,
            p_RX_CLK25_DIV                           = 5,
            p_TX_CLK25_DIV                           = 5,
            p_UCODEER_CLR                            = 0b0,

            # PCI Express Attributes
            p_PCS_PCIE_EN                            = "FALSE",

            # PCS Attributes
            p_PCS_RSVD_ATTR                          = 0x000000000000,

            # RX Buffer Attributes
            p_RXBUF_ADDR_MODE                        = "FAST",
            p_RXBUF_EIDLE_HI_CNT                     = 0b1000,
            p_RXBUF_EIDLE_LO_CNT                     = 0b0000,
            p_RXBUF_EN                               = "TRUE",
            p_RX_BUFFER_CFG                          = 0b000000,
            p_RXBUF_RESET_ON_CB_CHANGE               = "TRUE",
            p_RXBUF_RESET_ON_COMMAALIGN              = "FALSE",
            p_RXBUF_RESET_ON_EIDLE                   = "FALSE",
            p_RXBUF_RESET_ON_RATE_CHANGE             = "TRUE",
            p_RXBUFRESET_TIME                        = 0b00001,
            p_RXBUF_THRESH_OVFLW                     = 61,
            p_RXBUF_THRESH_OVRD                      = "FALSE",
            p_RXBUF_THRESH_UNDFLW                    = 4,
            p_RXDLY_CFG                              = 0x001F,
            p_RXDLY_LCFG                             = 0x030,
            p_RXDLY_TAP_CFG                          = 0x0000,
            p_RXPH_CFG                               = 0xC00002,
            p_RXPHDLY_CFG                            = 0x084020,
            p_RXPH_MONITOR_SEL                       = 0b00000,
            p_RX_XCLK_SEL                            = "RXREC",
            p_RX_DDI_SEL                             = 0b000000,
            p_RX_DEFER_RESET_BUF_EN                  = "TRUE",

            # CDR Attributes
            p_RXCDR_CFG                              = 0x0001107FE086021101010,
            p_RXCDR_FR_RESET_ON_EIDLE                = 0b0,
            p_RXCDR_HOLD_DURING_EIDLE                = 0b0,
            p_RXCDR_PH_RESET_ON_EIDLE                = 0b0,
            p_RXCDR_LOCK_CFG                         = 0b001001,

            # RX Initialization and Reset Attributes
            p_RXCDRFREQRESET_TIME                    = 0b00001,
            p_RXCDRPHRESET_TIME                      = 0b00001,
            p_RXISCANRESET_TIME                      = 0b00001,
            p_RXPCSRESET_TIME                        = 0b00001,
            p_RXPMARESET_TIME                        = 0b00011,

            # RX OOB Signaling Attributes
            p_RXOOB_CFG                              = 0b0000110,

            # RX Gearbox Attributes
            p_RXGEARBOX_EN                           = "FALSE",
            p_GEARBOX_MODE                           = 0b000,

            # PRBS Detection Attribute
            p_RXPRBS_ERR_LOOPBACK                    = 0b0,

            # Power-Down Attributes
            p_PD_TRANS_TIME_FROM_P2                  = 0x03c,
            p_PD_TRANS_TIME_NONE_P2                  = 0x3c,
            p_PD_TRANS_TIME_TO_P2                    = 0x64,

            # RX OOB Signaling Attributes
            p_SAS_MAX_COM                            = 64,
            p_SAS_MIN_COM                            = 36,
            p_SATA_BURST_SEQ_LEN                     = 0b0101,
            p_SATA_BURST_VAL                         = 0b100,
            p_SATA_EIDLE_VAL                         = 0b100,
            p_SATA_MAX_BURST                         = 8,
            p_SATA_MAX_INIT                          = 21,
            p_SATA_MAX_WAKE                          = 7,
            p_SATA_MIN_BURST                         = 4,
            p_SATA_MIN_INIT                          = 12,
            p_SATA_MIN_WAKE                          = 4,

            # RX Fabric Clock Output Control Attributes
            p_TRANS_TIME_RATE                        = 0x0E,

            # TX Buffer Attributes
            p_TXBUF_EN                               = "TRUE",
            p_TXBUF_RESET_ON_RATE_CHANGE             = "TRUE",
            p_TXDLY_CFG                              = 0x001F,
            p_TXDLY_LCFG                             = 0x030,
            p_TXDLY_TAP_CFG                          = 0x0000,
            p_TXPH_CFG                               = 0x0780,
            p_TXPHDLY_CFG                            = 0x084020,
            p_TXPH_MONITOR_SEL                       = 0b00000,
            p_TX_XCLK_SEL                            = "TXOUT",

            # FPGA TX Interface Attributes
            p_TX_DATA_WIDTH                          = 20,

            # TX Configurable Driver Attributes
            p_TX_DEEMPH0                             = 0b000000,
            p_TX_DEEMPH1                             = 0b000000,
            p_TX_EIDLE_ASSERT_DELAY                  = 0b110,
            p_TX_EIDLE_DEASSERT_DELAY                = 0b100,
            p_TX_LOOPBACK_DRIVE_HIZ                  = "FALSE",
            p_TX_MAINCURSOR_SEL                      = 0b0,
            p_TX_DRIVE_MODE                          = "DIRECT",
            p_TX_MARGIN_FULL_0                       = 0b1001110,
            p_TX_MARGIN_FULL_1                       = 0b1001001,
            p_TX_MARGIN_FULL_2                       = 0b1000101,
            p_TX_MARGIN_FULL_3                       = 0b1000010,
            p_TX_MARGIN_FULL_4                       = 0b1000000,
            p_TX_MARGIN_LOW_0                        = 0b1000110,
            p_TX_MARGIN_LOW_1                        = 0b1000100,
            p_TX_MARGIN_LOW_2                        = 0b1000010,
            p_TX_MARGIN_LOW_3                        = 0b1000000,
            p_TX_MARGIN_LOW_4                        = 0b1000000,

            # TX Gearbox Attributes
            p_TXGEARBOX_EN                           = "FALSE",

            # TX Initialization and Reset Attributes
            p_TXPCSRESET_TIME                        = 0b00001,
            p_TXPMARESET_TIME                        = 0b00001,

            # TX Receiver Detection Attributes
            p_TX_RXDETECT_CFG                        = 0x1832,
            p_TX_RXDETECT_REF                        = 0b100,

            # JTAG Attributes
            p_ACJTAG_DEBUG_MODE                      = 0b0,
            p_ACJTAG_MODE                            = 0b0,
            p_ACJTAG_RESET                           = 0b0,

            # CDR Attributes
            p_CFOK_CFG                               = 0x49000040E80,
            p_CFOK_CFG2                              = 0b0100000,
            p_CFOK_CFG3                              = 0b0100000,
            p_CFOK_CFG4                              = 0b0,
            p_CFOK_CFG5                              = 0x0,
            p_CFOK_CFG6                              = 0b0000,
            p_RXOSCALRESET_TIME                      = 0b00011,
            p_RXOSCALRESET_TIMEOUT                   = 0b00000,

            # PMA Attributes
            p_CLK_COMMON_SWING                       = 0b0,
            p_RX_CLKMUX_EN                           = 0b1,
            p_TX_CLKMUX_EN                           = 0b1,
            p_ES_CLK_PHASE_SEL                       = 0b0,
            p_USE_PCS_CLK_PHASE_SEL                  = 0b0,
            p_PMA_RSV6                               = 0b0,
            p_PMA_RSV7                               = 0b0,

            # TX Configuration Driver Attributes
            p_TX_PREDRIVER_MODE                      = 0b0,
            p_PMA_RSV5                               = 0b0,
            p_SATA_PLL_CFG                           = "VCO_3000MHZ",

            # RX Fabric Clock Output Control Attributes
            p_RXOUT_DIV                              = 4,

            # TX Fabric Clock Output Control Attributes
            p_TXOUT_DIV                              = 4,

            # RX Phase Interpolator Attributes
            p_RXPI_CFG0                              = 0b000,
            p_RXPI_CFG1                              = 0b1,
            p_RXPI_CFG2                              = 0b1,

            # RX Equalizer Attributes
            p_ADAPT_CFG0                             = 0x00000,
            p_RXLPMRESET_TIME                        = 0b0001111,
            p_RXLPM_BIAS_STARTUP_DISABLE             = 0b0,
            p_RXLPM_CFG                              = 0b0110,
            p_RXLPM_CFG1                             = 0b0,
            p_RXLPM_CM_CFG                           = 0b0,
            p_RXLPM_GC_CFG                           = 0b111100010,
            p_RXLPM_GC_CFG2                          = 0b001,
            p_RXLPM_HF_CFG                           = 0b00001111110000,
            p_RXLPM_HF_CFG2                          = 0b01010,
            p_RXLPM_HF_CFG3                          = 0b0000,
            p_RXLPM_HOLD_DURING_EIDLE                = 0b0,
            p_RXLPM_INCM_CFG                         = 0b0,
            p_RXLPM_IPCM_CFG                         = 0b1,
            p_RXLPM_LF_CFG                           = 0b000000001111110000,
            p_RXLPM_LF_CFG2                          = 0b01010,
            p_RXLPM_OSINT_CFG                        = 0b100,

            # TX Phase Interpolator PPM Controller Attributes
            p_TXPI_CFG0                              = 0b00,
            p_TXPI_CFG1                              = 0b00,
            p_TXPI_CFG2                              = 0b00,
            p_TXPI_CFG3                              = 0b0,
            p_TXPI_CFG4                              = 0b0,
            p_TXPI_CFG5                              = 0b000,
            p_TXPI_GREY_SEL                          = 0b0,
            p_TXPI_INVSTROBE_SEL                     = 0b0,
            p_TXPI_PPMCLK_SEL                        = "TXUSRCLK2",
            p_TXPI_PPM_CFG                           = 0x00,
            p_TXPI_SYNFREQ_PPM                       = 0b001,

            # LOOPBACK Attributes
            p_LOOPBACK_CFG                           = 0b0,
            p_PMA_LOOPBACK_CFG                       = 0b0,

            # RX OOB Signalling Attributes
            p_RXOOB_CLK_CFG                          = "PMA",

            # TX OOB Signalling Attributes
            p_TXOOB_CFG                              = 0b0,

            # RX Buffer Attributes
            p_RXSYNC_MULTILANE                       = 0b0,
            p_RXSYNC_OVRD                            = 0b0,
            p_RXSYNC_SKIP_DA                         = 0b0,

            # TX Buffer Attributes
            p_TXSYNC_MULTILANE                       = 0b0,
            p_TXSYNC_OVRD                            = 0b0,
            p_TXSYNC_SKIP_DA                         = 0b0
        )
        gtp_params.update(
            # CPLL Ports
            i_GTRSVD                         = 0b0000000000000000,
            i_PCSRSVDIN                      = 0b0000000000000000,
            i_TSTIN                          = 0b11111111111111111111,
            # Channel - DRP Ports
            i_DRPADDR                        = drpaddr,
            i_DRPCLK                         = ClockSignal(),
            i_DRPDI                          = drpdi,
            o_DRPDO                          = drpdo,
            i_DRPEN                          = drpen,
            o_DRPRDY                         = drprdy,
            i_DRPWE                          = drpwe,
            # FPGA TX Interface Datapath Configuration
            i_TX8B10BEN                      = 0,
            # Loopback Ports
            i_LOOPBACK                       = 0,
            # PCI Express Ports
            o_PHYSTATUS                      = Open(),
            i_RXRATE                         = 0,
            o_RXVALID                        = Open(),
            # PMA Reserved Ports
            i_PMARSVDIN3                     = 0b0,
            i_PMARSVDIN4                     = 0b0,
            # Power-Down Ports
            i_RXPD                           = 0b00,
            i_TXPD                           = 0b00,
            # RX 8B/10B Decoder Ports
            i_SETERRSTATUS                   = 0,
            # RX Initialization and Reset Ports
            i_EYESCANRESET                   = 0,
            i_RXUSERRDY                      = rx_mmcm_locked,
            # RX Margin Analysis Ports
            o_EYESCANDATAERROR               = Open(),
            i_EYESCANMODE                    = 0,
            i_EYESCANTRIGGER                 = 0,
            # Receive Ports
            i_CLKRSVD0                       = 0,
            i_CLKRSVD1                       = 0,
            i_DMONFIFORESET                  = 0,
            i_DMONITORCLK                    = 0,
            o_RXPMARESETDONE                 = rx_pma_reset_done,
            i_SIGVALIDCLK                    = 0,
            # Receive Ports - CDR Ports
            i_RXCDRFREQRESET                 = 0,
            i_RXCDRHOLD                      = 0,
            o_RXCDRLOCK                      = Open(),
            i_RXCDROVRDEN                    = 0,
            i_RXCDRRESET                     = 0,
            i_RXCDRRESETRSV                  = 0,
            i_RXOSCALRESET                   = 0,
            i_RXOSINTCFG                     = 0b0010,
            o_RXOSINTDONE                    = Open(),
            i_RXOSINTHOLD                    = 0,
            i_RXOSINTOVRDEN                  = 0,
            i_RXOSINTPD                      = 0,
            o_RXOSINTSTARTED                 = Open(),
            i_RXOSINTSTROBE                  = 0,
            o_RXOSINTSTROBESTARTED           = Open(),
            i_RXOSINTTESTOVRDEN              = 0,
            # Receive Ports - Clock Correction Ports
            o_RXCLKCORCNT                    = Open(),
            # Receive Ports - FPGA RX Interface Datapath Configuration
            i_RX8B10BEN                      = 0,
            # Receive Ports - FPGA RX Interface Ports
            o_RXDATA                         = Cat(rx_data[:8], rx_data[10:18]),
            i_RXUSRCLK                       = ClockSignal("eth_rx_half"),
            i_RXUSRCLK2                      = ClockSignal("eth_rx_half"),
            # Receive Ports - Pattern Checker Ports
            o_RXPRBSERR                      = Open(),
            i_RXPRBSSEL                      = 0,
            # Receive Ports - Pattern Checker ports
            i_RXPRBSCNTRESET                 = 0,
            # Receive Ports - RX 8B/10B Decoder Ports
            o_RXCHARISCOMMA                  = Open(),
            o_RXCHARISK                      = Cat(rx_data[8], rx_data[18]),
            o_RXDISPERR                      = Cat(rx_data[9], rx_data[19]),
            o_RXNOTINTABLE                   = Open(),
            # Receive Ports - RX AFE Ports
            i_GTPRXN                         = data_pads.rxn,
            i_GTPRXP                         = data_pads.rxp,
            i_PMARSVDIN2                     = 0b0,
            o_PMARSVDOUT0                    = Open(),
            o_PMARSVDOUT1                    = Open(),
            # Receive Ports - RX Buffer Bypass Ports
            i_RXBUFRESET                     = 0,
            o_RXBUFSTATUS                    = Open(),
            i_RXDDIEN                        = 0,
            i_RXDLYBYPASS                    = 1,
            i_RXDLYEN                        = 0,
            i_RXDLYOVRDEN                    = 0,
            i_RXDLYSRESET                    = 0,
            o_RXDLYSRESETDONE                = Open(),
            i_RXPHALIGN                      = 0,
            o_RXPHALIGNDONE                  = Open(),
            i_RXPHALIGNEN                    = 0,
            i_RXPHDLYPD                      = 0,
            i_RXPHDLYRESET                   = 0,
            o_RXPHMONITOR                    = Open(),
            i_RXPHOVRDEN                     = 0,
            o_RXPHSLIPMONITOR                = Open(),
            o_RXSTATUS                       = Open(),
            i_RXSYNCALLIN                    = 0,
            o_RXSYNCDONE                     = Open(),
            i_RXSYNCIN                       = 0,
            i_RXSYNCMODE                     = 0,
            o_RXSYNCOUT                      = Open(),
            # Receive Ports - RX Byte and Word Alignment Ports
            o_RXBYTEISALIGNED                = Open(),
            o_RXBYTEREALIGN                  = Open(),
            o_RXCOMMADET                     = Open(),
            i_RXCOMMADETEN                   = 0,
            i_RXMCOMMAALIGNEN                = 0,
            i_RXPCOMMAALIGNEN                = 0,
            i_RXSLIDE                        = 0,
            # Receive Ports - RX Channel Bonding Ports
            o_RXCHANBONDSEQ                  = Open(),
            i_RXCHBONDEN                     = 0,
            i_RXCHBONDI                      = 0b0000,
            i_RXCHBONDLEVEL                  = 0,
            i_RXCHBONDMASTER                 = 0,
            o_RXCHBONDO                      = Open(),
            i_RXCHBONDSLAVE                  = 0,
            # Receive Ports - RX Channel Bonding Ports
            o_RXCHANISALIGNED                = Open(),
            o_RXCHANREALIGN                  = Open(),
            # Receive Ports - RX Decision Feedback Equalizer
            o_DMONITOROUT                    = Open(),
            i_RXADAPTSELTEST                 = 0,
            i_RXDFEXYDEN                     = 0,
            i_RXOSINTEN                      = 0b1,
            i_RXOSINTID0                     = 0,
            i_RXOSINTNTRLEN                  = 0,
            o_RXOSINTSTROBEDONE              = Open(),
            # Receive Ports - RX Driver,OOB signalling,Coupling and Eq.,CDR
            i_RXLPMLFOVRDEN                  = 0,
            i_RXLPMOSINTNTRLEN               = 0,
            # Receive Ports - RX Equalizer Ports
            i_RXLPMHFHOLD                    = 0,
            i_RXLPMHFOVRDEN                  = 0,
            i_RXLPMLFHOLD                    = 0,
            i_RXOSHOLD                       = 0,
            i_RXOSOVRDEN                     = 0,
            # Receive Ports - RX Fabric ClocK Output Control Ports
            o_RXRATEDONE                     = Open(),
            # Receive Ports - RX Fabric Clock Output Control Ports
            i_RXRATEMODE                     = 0b0,
            # Receive Ports - RX Fabric Output Control Ports
            o_RXOUTCLK                       = self.rxoutclk,
            o_RXOUTCLKFABRIC                 = Open(),
            o_RXOUTCLKPCS                    = Open(),
            i_RXOUTCLKSEL                    = 0b010,
            # Receive Ports - RX Gearbox Ports
            o_RXDATAVALID                    = Open(),
            o_RXHEADER                       = Open(),
            o_RXHEADERVALID                  = Open(),
            o_RXSTARTOFSEQ                   = Open(),
            i_RXGEARBOXSLIP                  = 0,
            # Receive Ports - RX Initialization and Reset Ports
            i_GTRXRESET                      = rx_reset,
            i_RXLPMRESET                     = 0,
            i_RXOOBRESET                     = 0,
            i_RXPCSRESET                     = 0,
            i_RXPMARESET                     = 0,
            # Receive Ports - RX OOB Signaling ports
            o_RXCOMSASDET                    = Open(),
            o_RXCOMWAKEDET                   = Open(),
            o_RXCOMINITDET                   = Open(),
            o_RXELECIDLE                     = Open(),
            i_RXELECIDLEMODE                 = 0b11,
            # Receive Ports - RX Polarity Control Ports
            i_RXPOLARITY                     = 0,
            # Receive Ports -RX Initialization and Reset Ports
            o_RXRESETDONE                    = rx_reset_done,
            # TX Buffer Bypass Ports
            i_TXPHDLYTSTCLK                  = 0,
            # TX Configurable Driver Ports
            i_TXPOSTCURSOR                   = 0b00000,
            i_TXPOSTCURSORINV                = 0,
            i_TXPRECURSOR                    = 0,
            i_TXPRECURSORINV                 = 0,
            # TX Fabric Clock Output Control Ports
            i_TXRATEMODE                     = 0,
            # TX Initialization and Reset Ports
            i_CFGRESET                       = 0,
            i_GTTXRESET                      = tx_reset,
            o_PCSRSVDOUT                     = Open(),
            i_TXUSERRDY                      = tx_mmcm_locked,
            # TX Phase Interpolator PPM Controller Ports
            i_TXPIPPMEN                      = 0,
            i_TXPIPPMOVRDEN                  = 0,
            i_TXPIPPMPD                      = 0,
            i_TXPIPPMSEL                     = 1,
            i_TXPIPPMSTEPSIZE                = 0,
            # Transceiver Reset Mode Operation
            i_GTRESETSEL                     = 0,
            i_RESETOVRD                      = 0,
            # Transmit Ports
            o_TXPMARESETDONE                 = Open(),
            # Transmit Ports - Configurable Driver Ports
            i_PMARSVDIN0                     = 0b0,
            i_PMARSVDIN1                     = 0b0,
            # Transmit Ports - FPGA TX Interface Ports
            i_TXDATA                         = Cat(tx_data[:8], tx_data[10:18]),
            i_TXUSRCLK                       = ClockSignal("eth_tx_half"),
            i_TXUSRCLK2                      = ClockSignal("eth_tx_half"),
            # Transmit Ports - PCI Express Ports
            i_TXELECIDLE                     = 0,
            i_TXMARGIN                       = 0,
            i_TXRATE                         = 0,
            i_TXSWING                        = 0,
            # Transmit Ports - Pattern Generator Ports
            i_TXPRBSFORCEERR                 = 0,
            # Transmit Ports - TX 8B/10B Encoder Ports
            i_TX8B10BBYPASS                  = 0,
            i_TXCHARDISPMODE                 = Cat(tx_data[9], tx_data[19]),
            i_TXCHARDISPVAL                  = Cat(tx_data[8], tx_data[18]),
            i_TXCHARISK                      = 0,
            # Transmit Ports - TX Buffer Bypass Ports
            i_TXDLYBYPASS                    = 1,
            i_TXDLYEN                        = 0,
            i_TXDLYHOLD                      = 0,
            i_TXDLYOVRDEN                    = 0,
            i_TXDLYSRESET                    = 0,
            o_TXDLYSRESETDONE                = Open(),
            i_TXDLYUPDOWN                    = 0,
            i_TXPHALIGN                      = 0,
            o_TXPHALIGNDONE                  = Open(),
            i_TXPHALIGNEN                    = 0,
            i_TXPHDLYPD                      = 0,
            i_TXPHDLYRESET                   = 0,
            i_TXPHINIT                       = 0,
            o_TXPHINITDONE                   = Open(),
            i_TXPHOVRDEN                     = 0,
            # Transmit Ports - TX Buffer Ports
            o_TXBUFSTATUS                    = Open(),
            # Transmit Ports - TX Buffer and Phase Alignment Ports
            i_TXSYNCALLIN                    = 0,
            o_TXSYNCDONE                     = Open(),
            i_TXSYNCIN                       = 0,
            i_TXSYNCMODE                     = 0,
            o_TXSYNCOUT                      = Open(),
            # Transmit Ports - TX Configurable Driver Ports
            o_GTPTXN                         = data_pads.txn,
            o_GTPTXP                         = data_pads.txp,
            i_TXBUFDIFFCTRL                  = 0b100,
            i_TXDEEMPH                       = 0,
            i_TXDIFFCTRL                     = 0b1000,
            i_TXDIFFPD                       = 0,
            i_TXINHIBIT                      = 0,
            i_TXMAINCURSOR                   = 0b0000000,
            i_TXPISOPD                       = 0,
            # Transmit Ports - TX Fabric Clock Output Control Ports
            o_TXOUTCLK                       = self.txoutclk,
            o_TXOUTCLKFABRIC                 = Open(),
            o_TXOUTCLKPCS                    = Open(),
            i_TXOUTCLKSEL                    = 0b010,
            o_TXRATEDONE                     = Open(),
            # Transmit Ports - TX Gearbox Ports
            o_TXGEARBOXREADY                 = Open(),
            i_TXHEADER                       = 0,
            i_TXSEQUENCE                     = 0,
            i_TXSTARTSEQ                     = 0,
            # Transmit Ports - TX Initialization and Reset Ports
            i_TXPCSRESET                     = 0,
            i_TXPMARESET                     = 0,
            o_TXRESETDONE                    = tx_reset_done,
            # Transmit Ports - TX OOB signalling Ports
            o_TXCOMFINISH                    = Open(),
            i_TXCOMINIT                      = 0,
            i_TXCOMSAS                       = 0,
            i_TXCOMWAKE                      = 0,
            i_TXPDELECIDLEMODE               = 0,
            # Transmit Ports - TX Polarity Control Ports
            i_TXPOLARITY                     = 0,
            # Transmit Ports - TX Receiver Detection Ports
            i_TXDETECTRX                     = 0,
            # Transmit Ports - pattern Generator Ports
            i_TXPRBSSEL                      = 0
        )
        if qpll_channel.index == 0:
            gtp_params.update(
                # Clocking Ports
                i_RXSYSCLKSEL = 0b00,
                i_TXSYSCLKSEL = 0b00,
                # GTPE2_CHANNEL Clocking Ports
                i_PLL0CLK     = qpll_channel.clk,
                i_PLL0REFCLK  = qpll_channel.refclk,
                i_PLL1CLK     = 0,
                i_PLL1REFCLK  = 0,
            )
        elif qpll_channel.index == 1:
            gtp_params.update(
                # Clocking Ports
                i_RXSYSCLKSEL = 0b11,
                i_TXSYSCLKSEL = 0b11,
                # GTPE2_CHANNEL Clocking Ports
                i_PLL0CLK     = 0,
                i_PLL0REFCLK  = 0,
                i_PLL1CLK     = qpll_channel.clk,
                i_PLL1REFCLK  = qpll_channel.refclk,
            )
        else:
            raise ValueError
        self.specials += Instance("GTPE2_CHANNEL", **gtp_params)

        # Get 125MHz clocks back - the GTP junk insists on outputting 62.5MHz.
        txoutclk_rebuffer = Signal()
        self.specials += Instance("BUFH", i_I=self.txoutclk, o_O=txoutclk_rebuffer)
        rxoutclk_rebuffer = Signal()
        self.specials += Instance("BUFG", i_I=self.rxoutclk, o_O=rxoutclk_rebuffer)

        tx_mmcm_fb        = Signal()
        tx_mmcm_reset     = Signal(reset=1)
        clk_tx_unbuf      = Signal()
        clk_tx_half_unbuf = Signal()
        self.specials += [
            Instance("MMCME2_BASE",
                p_CLKIN1_PERIOD    = 16.0,
                i_CLKIN1           = txoutclk_rebuffer,
                i_RST              = tx_mmcm_reset,

                o_CLKFBOUT         = tx_mmcm_fb,
                i_CLKFBIN          = tx_mmcm_fb,

                p_CLKFBOUT_MULT_F  = 16,
                o_LOCKED           = tx_mmcm_locked,
                p_DIVCLK_DIVIDE    = 1,

                p_CLKOUT0_DIVIDE_F = 16,
                o_CLKOUT0          = clk_tx_half_unbuf,
                p_CLKOUT1_DIVIDE   = 8,
                o_CLKOUT1          = clk_tx_unbuf,
            ),
            Instance("BUFH",
                i_I = clk_tx_half_unbuf,
                o_O = self.cd_eth_tx_half.clk,
            ),
            Instance("BUFH",
                i_I = clk_tx_unbuf,
                o_O = self.cd_eth_tx.clk,
            ),
            AsyncResetSynchronizer(self.cd_eth_tx, ~tx_mmcm_locked)
        ]

        rx_mmcm_fb        = Signal()
        rx_mmcm_reset     = Signal(reset=1)
        clk_rx_unbuf      = Signal()
        clk_rx_half_unbuf = Signal()
        self.specials += [
            Instance("MMCME2_BASE",
                p_CLKIN1_PERIOD    = 16.0,
                i_CLKIN1           = rxoutclk_rebuffer,
                i_RST              = rx_mmcm_reset,

                o_CLKFBOUT         = rx_mmcm_fb,
                i_CLKFBIN          = rx_mmcm_fb,

                p_CLKFBOUT_MULT_F  = 16,
                o_LOCKED           = rx_mmcm_locked,
                p_DIVCLK_DIVIDE    = 1,

                p_CLKOUT0_DIVIDE_F = 16,
                o_CLKOUT0          = clk_rx_half_unbuf,
                p_CLKOUT1_DIVIDE   = 8,
                o_CLKOUT1          = clk_rx_unbuf,
            ),
            Instance("BUFG",
                i_I = clk_rx_half_unbuf,
                o_O = self.cd_eth_rx_half.clk,
            ),
            Instance("BUFG",
                i_I = clk_rx_unbuf,
                o_O = self.cd_eth_rx.clk,
            ),
            AsyncResetSynchronizer(self.cd_eth_rx, ~rx_mmcm_locked)
        ]

        # Transceiver init
        tx_init = GTPTxInit(sys_clk_freq)
        self.submodules += tx_init
        self.comb += [
            qpll_channel.reset.eq(tx_init.qpll_reset),
            tx_init.qpll_lock.eq(qpll_channel.lock),
            tx_reset.eq(tx_init.tx_reset)
        ]
        self.sync += tx_mmcm_reset.eq(~qpll_channel.lock)
        tx_mmcm_reset.attr.add("no_retiming")

        rx_init = GTPRxInit(sys_clk_freq)
        self.submodules += rx_init
        self.comb += [
            rx_init.enable.eq(tx_init.done),
            rx_reset.eq(rx_init.rx_reset),

            rx_init.rx_pma_reset_done.eq(rx_pma_reset_done),
            drpaddr.eq(rx_init.drpaddr),
            drpen.eq(rx_init.drpen),
            drpdi.eq(rx_init.drpdi),
            rx_init.drprdy.eq(drprdy),
            rx_init.drpdo.eq(drpdo),
            drpwe.eq(rx_init.drpwe)
        ]
        ps_restart = PulseSynchronizer("eth_tx", "sys")
        self.submodules += ps_restart
        self.comb += [
            ps_restart.i.eq(pcs.restart),
            rx_init.restart.eq(ps_restart.o)
        ]

        # Assume CDR lock time is 50,000 UI as per DS183 and similar to what the Xilinx wizards does.
        cdr_lock_time = round(sys_clk_freq*50e3/1.25e9)
        cdr_lock_counter = Signal(max=cdr_lock_time+1)
        cdr_locked = Signal()
        self.sync += [
            If(rx_reset,
                cdr_locked.eq(0),
                cdr_lock_counter.eq(0)
            ).Elif(cdr_lock_counter != cdr_lock_time,
                cdr_lock_counter.eq(cdr_lock_counter + 1)
            ).Else(
                cdr_locked.eq(1)
            ),
            rx_mmcm_reset.eq(~cdr_locked)
        ]
        rx_mmcm_reset.attr.add("no_retiming")

        # Gearbox and PCS connection
        gearbox = Gearbox()
        self.submodules += gearbox

        self.comb += [
            tx_data.eq(gearbox.tx_data_half),
            gearbox.rx_data_half.eq(rx_data),

            gearbox.tx_data.eq(pcs.tbi_tx),
            pcs.tbi_rx.eq(gearbox.rx_data)
        ]
